package org.aksw.xoperator.roster;

import java.io.StringWriter;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ThreadPoolExecutor;

import org.aksw.xoperator.Constants;
import org.aksw.xoperator.OPO;
import org.aksw.xoperator.SIOC;
import org.aksw.xoperator.XOP;
import org.aksw.xoperator.conf.AgentConfiguration;
import org.aksw.xoperator.roster.IRosterManager;
import org.aksw.xoperator.xmpp.IJabberP2P;
import org.aksw.xoperator.xmpp.JabberClientManager;
import org.aksw.xoperator.xmpp.JabberUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jivesoftware.smack.Roster;
import org.jivesoftware.smack.RosterEntry;
import org.jivesoftware.smack.RosterGroup;
import org.jivesoftware.smack.RosterListener;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smackx.ServiceDiscoveryManager;
import org.jivesoftware.smackx.packet.VCard;

import com.hp.hpl.jena.datatypes.RDFDatatype;
import com.hp.hpl.jena.datatypes.xsd.XSDDatatype;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.ModelFactory;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.rdf.model.ResourceFactory;
import com.hp.hpl.jena.rdf.model.SimpleSelector;
import com.hp.hpl.jena.rdf.model.Statement;
import com.hp.hpl.jena.rdf.model.StmtIterator;
import com.hp.hpl.jena.shared.Lock;
import com.hp.hpl.jena.sparql.vocabulary.FOAF;
import com.hp.hpl.jena.update.UpdateAction;
import com.hp.hpl.jena.update.UpdateFactory;
import com.hp.hpl.jena.update.UpdateRequest;
import com.hp.hpl.jena.vocabulary.DC;
import com.hp.hpl.jena.vocabulary.RDF;
import com.hp.hpl.jena.vocabulary.RDFS;

public class RosterManager implements IRosterManager {
	
	
	private IJabberP2P xmppAccess;

	private ThreadPoolExecutor executor;

	private Roster roster;

	private AgentConfiguration aconf;

	private Log log = LogFactory.getLog(RosterManager.class);

	private Model model;

	public Model rosterAsModel() {
		return model;
	}

	public RosterManager(JabberClientManager jcmanager,
			AgentConfiguration aconf, ThreadPoolExecutor executor) {
		super();
		this.executor = executor;
		this.roster = jcmanager.getRoster();
		this.aconf = aconf;
		jcmanager.addRosterListener(new Listener());

		// init the default model
		model = ModelFactory.createDefaultModel();
	}

	/**
	 * converts the roster into an rdf representation. This represenation is
	 * quite bare and gets further filled with vcard and presence informatiom by
	 * the appropriate methods.
	 */
	public void convertRoster() {

		model.setNsPrefix("owl", "http://www.w3.org/2002/07/owl#");
		model.setNsPrefix("rdfs", "http://www.w3.org/2000/01/rdf-schema#");
		model.setNsPrefix("foaf", "http://xmlns.com/foaf/0.1/");
		model.setNsPrefix("xsd", "http://www.w3.org/2001/XMLSchema#");
		model.setNsPrefix("xop", "http://ns.aksw.org/xop/");


		Resource me = model.createResource("xmpp:"
				+ aconf.getJabberClientConfiguration().getMainAccount()
				.getJid());
		
		
		
		
		// add all the users

		for (RosterEntry rentry : roster.getEntries()) {
			Resource agentEntry = model.createResource("xmpp:"
					+ rentry.getUser());
			me.addProperty(FOAF.member, agentEntry);
			me.addProperty(FOAF.knows, agentEntry);
			agentEntry.addProperty(RDF.type, SIOC.User);
			agentEntry.addProperty(FOAF.accountName, rentry.getUser());
			agentEntry.addProperty(FOAF.accountServiceHomepage, "unable to determine");
			agentEntry.addProperty(RDFS.label, " Jabber account of " +
					rentry.getName() != null ? rentry.getName() : rentry
							.getUser());	
		}

	}

	public void addPresenceInformation(Presence presence) {
		String jid = StringUtils.parseBareAddress(presence.getFrom());
		String jidresource = presence.getFrom();
		
				
		model.enterCriticalSection(Lock.WRITE);
		if(presence.getType().equals(Presence.Type.available)){
			//adding a new Presence
			Resource chatAccount  = model.createResource("xmpp:"+jid);
			chatAccount.addProperty(RDF.type, SIOC.User);
			
			Resource onlinePresence = model.createResource(jidresource);
			onlinePresence.addProperty(RDF.type, OPO.OnlinePresence);
			onlinePresence.addProperty(OPO.declaresOn, chatAccount);
			onlinePresence.addProperty(OPO.startTime, new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss").format(new Date()));
			
			if(presence.getMode().equals(Presence.Mode.available)||presence.getMode().equals(Presence.Mode.chat)){
				onlinePresence.addProperty(OPO.hasPresenceComponent, OPO.Active);
			}else{
				onlinePresence.addProperty(OPO.hasPresenceComponent, OPO.Busy);
			}

		}else if(presence.getType().equals(Presence.Type.unavailable)){
			//delete all Statements where the presence is subject
			model.removeAll(ResourceFactory.createResource("xmpp:"+jidresource), null, null);
			
		}
		
		model.leaveCriticalSection();
		
	}

	/**
	 * converts a vcard into foaf and adds this information to the local model
	 * 
	 * @param incomingVcard
	 */
	public void addVCardInformation(VCard incomingVcard) {
		
		//look for a good identifier, use the jabber id as fallback
		String id = "xmpp:" + incomingVcard.getFrom();
		if(incomingVcard.getEmailWork()!=null){
			id = "mailto:" +incomingVcard.getEmailWork();
		}
		if(incomingVcard.getEmailHome()!=null){
			id = "mailto:" + incomingVcard.getEmailHome();
		}
		
		
		Resource contact = model.createResource(id);
		
		contact.addProperty(RDF.type, FOAF.Agent);
		
		if (incomingVcard.getNickName() != null) {
			contact.addProperty(FOAF.nick, incomingVcard.getNickName());
		}
		if (incomingVcard.getEmailHome() != null) {
			contact.addProperty(FOAF.mbox, model.createResource("mailto:"
					+ incomingVcard.getEmailHome()));
		}
		if (incomingVcard.getEmailWork() != null) {
			contact.addProperty(FOAF.mbox, model.createResource("mailto:"
					+ incomingVcard.getEmailWork()));
		}
		if (incomingVcard.getFirstName() != null) {
			contact.addProperty(FOAF.firstName, incomingVcard.getFirstName());
		}
		if (incomingVcard.getLastName() != null) {
			contact.addProperty(FOAF.family_name, incomingVcard.getLastName());
		}
		if (incomingVcard.getPhoneHome("PREF") != null) {
			contact.addProperty(FOAF.phone, incomingVcard.getPhoneHome("PREF"));
		}
		if (incomingVcard.getPhoneWork("VOICE") != null) {
			contact
					.addProperty(FOAF.phone, incomingVcard
							.getPhoneWork("VOICE"));
		}
	}
	/**
	 * adds the information to the model, that a certain node is a xOperator
	 * 
	 * @param jid the jid with the resource where you can find the agent.
	 */
	private void addAgentInformation(String jid){
		model.enterCriticalSection(Lock.WRITE);
		
		Resource jidResource = model.createResource("xmpp:"+StringUtils.parseBareAddress(jid));
		jidResource.addProperty(XOP.isXOperator, model.createResource(jid));
		
		
		model.leaveCriticalSection();
	}



	

	private class Listener implements RosterListener {

		public void entriesAdded(Collection<String> arg0) {
			// TODO Auto-generated method stub

		}

		public void entriesDeleted(Collection<String> arg0) {
			// TODO Auto-generated method stub

		}

		public void entriesUpdated(Collection<String> arg0) {
			// TODO Auto-generated method stub

		}

		public void presenceChanged(Presence presence) {
			
			addPresenceInformation(presence);
			executor.execute(new XoperatorDiscoveryCallable(presence));
		}

	}



	private class XoperatorDiscoveryCallable implements Runnable {

		private Presence pres;

		public XoperatorDiscoveryCallable(Presence pres) {
			super();
			this.pres = pres;
		}

		public void run() {
			if (checkForAgent(pres.getFrom())) {
				addAgentInformation(pres.getFrom());
				log.info("Found an agent on: " + pres.getFrom());
			} else {
				log.info("Roster item: " + pres.getFrom() + " is not an agent");
			}

		}
		public boolean checkForAgent(String jid) {
			boolean agent = false;

			try {
				if (ServiceDiscoveryManager.getInstanceFor(xmppAccess.getConnection())
						.discoverInfo(jid).containsFeature(
								Constants.SPARQL_XMPP_SPEC_URL)) {
					log.info("detected agent at: " + jid);
					agent = true;
				}
			} catch (XMPPException e) {
				log.info("Error querying for the feature set of " + jid
						+ ", cause: " + e.getMessage());

				try {
					Thread.sleep(1000);
				} catch (InterruptedException e1) {
					log.error("Unable to sleep!");
				}
			}

			return agent;
		}
	}




	
	
	
	
	
	private void performUpdate(String updateRequest){
		UpdateRequest update = UpdateFactory.create(updateRequest);
		model.enterCriticalSection(Lock.WRITE);
		UpdateAction.execute(update, model);
		model.leaveCriticalSection();
	}

	public List<String> getAgentJids() {
		List<String> jids = new ArrayList<String>();
		StmtIterator stit = model.listStatements(null,XOP.isXOperator,ResourceFactory.createTypedLiteral(true));
		while(stit.hasNext()){
			//cheesy to hard code how long the uri is, isn't it?
			jids.add(stit.nextStatement().getSubject().getURI().substring(5));
		}
			
		
		return jids;
	}
	
	public void queryForRosterInformation() {
		executor.execute(new Runnable() {
			public void run() {
				for (RosterEntry rentry : xmppAccess.getConnection().getRoster()
						.getEntries()) {
					VCard vcard = new VCard();

					try {
						vcard.load(xmppAccess.getConnection(), rentry.getUser());
					} catch (XMPPException e) {
						log.info("Unable to load vcard for user: "
								+ rentry.getUser(), e);
					}
					addVCardInformation(vcard);
				}

				log.info("All user infos fetched");
			}
		});
	}
	
	

	

}
